在 React 中的資料流是一個非常值得探討的主題,首先明確一點,React 是所謂的單向資料流,因此大多數的時間裡,組件都會藉由傳入參數的方法(Props)來注入呼叫組件的變數,而自己組件內的我們通常都稱之為狀態(Stats),在通常的情況下,都是會從父組件傳入到子組件,已讓子組件可以使用父組件的資料。
傳入(props)最常見的地方,其實是在做一個類似組件的共用,如按鈕組件更換顏色,我們就可以單純使用 Props 來進行調度
const Btn = (props) => {
const { color, onClick } = props;
return (
<button style={{ backgroundColor: color,padding:'20px',color:'white' }} onClick={onClick}>
Click me {color}
</button>
);
};
<Btn color="red" onClick={() => console.log('red')} />
<Btn color="blue" onClick={() => console.log('blue')} />
以下是以個簡單的範例程式,我們在父組件上做了一個 useState 作為資料儲存,並且建立了一個刪除項目的函式,作為我們後續做刪除的調度使用。
在這個範例中,TodoItem 組件的任務是藉由 Props 傳入的內容,進行後續的顯示與操作,但這個組件本身會更新的的時機點,其實還是由父層傳入的 todo 是否變更來決定,在資料流上他其實是因為父層的 todo 這個 state 變動,在傳入時他也確認有異動,因此才會觸發畫面的更新。附帶一提,直接傳入 setTodoList Function 來使用也可以觸發異動,因為他都是在父層處理的資料流,而在設計上我個人不建議這樣傳入,因為相同類型的邏輯最好寫在父層會比較好,不過這個就看每個人喜歡的設計方式了
import React, { useState } from 'react';
const TodoItem = (props) => {
const { todo, removeTodo } = props;
return (
<article>
<h2>{todo.title}</h2>
<p>{todo.description}</p>
<button onClick={() => removeTodo(todo._id)}>Remove</button>
</article>
);
};
const TodoList = () => {
const [todoList, setTodoList] = useState([
{
_id: 1,
title: 'Todo 1',
description: 'Todo 1 description',
isCompleted: false,
},
{
_id: 1,
title: 'Todo 1',
description: 'Todo 1 description',
isCompleted: false,
},
]);
const removeTodo = (todoId) => {
const newTodoList = todoList.filter((todo) => todo._id !== todoId);
setTodoList(newTodoList);
};
return (
<article>
<h1>TodoList</h1>
<ul>
{todoList.map((todo) => (
<li key={todo._id}>
<TodoItem todo={todo} removeTodo={removeTodo} />
</li>
))}
</ul>
</article>
);
};
export default TodoList;
附帶一提,其實更新畫面的行為在邏輯上也是副作用,因為我們的主要行為其實是刪除一個項目,而連動運作了 UI 的更新,則不是我們原先預計的主要目的。
const Box = (props) => {
const { children } = props;
return (
<article>
以下是使用它所包含的元素
{children}
</article>
);
};
<Box>我是內容喔</Box>
自 React 誕生以來,就有一個有趣的問題產生,那就是我們到底應該怎麼去管理與儲存資料流,在上面 todo list 的範例中,我們會在一個功能內完成資料流的處理,因此並不會有太大的問題,但是如果是像有會員系統的網站,那會員資訊可能就沒有那麼合適儲存在某個 Component 中了,他可能會用在以下的地方。
而若將它放置在 APP 那一層(最父層),也會導致可能要傳入過多組件(Component),並且不易維護的問題,這一點可以在下面的範例中看到
所以在實務的設計上,我們會盡可能規避掉上面這種令人恐懼的資料流,但是實際需求上確實會有這樣的情況發生,也就是一些狀態與屬性需要可以跨組件進行使用,所以在這些得基礎上,我們會開始使用一些其他的資料流處理方式。
當跨組件的需求來臨時,我們可能會選擇像是 Redux 這類處理資料流的函式庫來處理,但在 React hook 中,其實也提供了一套非常方便的機制,而這就是 useContext。
在 useContext 的使用上,我們需要用 createContext 建立一個實體的 Context 來存儲資料,並且這個資料我們可以在起他的組件中,使用 useContext 來調度到他,以下是個簡單的範例。
import React, { createContext, useContext } from 'react';
const MyContext = createContext();
function ParentComponent() {
return (
<MyContext.Provider value="Hello from Context!">
<ChildComponent />
</MyContext.Provider>
);
}
function ChildComponent() {
const data = useContext(MyContext);
return <p>{data}</p>;
}
在上面的範例中,我們的 MyContext.Provider 包覆了 ChildComponent,所以 ChildComponent 可以取得到 MyContext value 中所帶有的資料。